﻿using System.Collections.Generic;
using System.Windows;
using Brjnk.Common;
using Brjnk.GpxExplorer.ViewModel;

namespace Brjnk.GpxExplorer.Computing
{
    public class FindPositionHelper
    {
        private FindPositionArgs args;
        private IEnumerable<IEnumerable<IPoint>> segments;

        public FindPositionHelper(FindPositionArgs args, IEnumerable<IEnumerable<IPoint>> segments)
        {
            this.args = args;
            this.segments = segments;
        }

        private List<FindPositionResult> retVal;
        private IPoint previous;

        //následující pole identifikují dosud nejbližší nalezený bod
        private IPoint nearestPrevious;

        private IPoint nearestNext;
        private double nearestParameter;
        private double nearestDistance;

        public IEnumerable<FindPositionResult> GetResult(GpsCoordinates searched)
        {
            retVal = new List<FindPositionResult>();

            previous = null;
            nearestPrevious = null;
            nearestNext = null;
            nearestDistance = 0;
            nearestParameter = 0;

            foreach (var segment in segments)
            {
                foreach (var point in segment)
                {
                    if (previous != null)
                    {
                        ProcessPoint(searched, previous, point);
                        ProcessLineSegment(searched, previous, point);
                    }
                    previous = point;
                }
                if (args.SearchInGaps == false) previous = null;
            }

            if (nearestPrevious != null) retVal.Add(CreateResult());

            return retVal;
        }

        private void ProcessLineSegment(GpsCoordinates searched, IPoint point1, IPoint point2)
        {
            if (point1.PositionI == point2.PositionI) return;
            Point p1 = (Point)point1.PositionI;
            Point p2 = (Point)point2.PositionI;

            var r = LineSegmentHelper.GetNearestPoint(p1, p2, (Point)searched);
            var d = searched - (GpsCoordinates)r.NearestPoint;
            Process(point1, point2, LineSegmentHelper.GetParameter(p1, p2, r.NearestPoint), d);
        }

        /// <summary>
        /// Zpracuje bod stopy. Zpracovávaný bod je previous, next je pouze předán metodě Process()
        /// </summary>
        private void ProcessPoint(GpsCoordinates searched, IPoint previous, IPoint next)
        {
            var d = (searched - previous.PositionI);
            Process(previous, next, 0, d);
        }

        private void Process(IPoint previousPoint, IPoint nextPoint, double parameter, Distance d)
        {
            if (d < args.Limit)
            {
                if (nearestPrevious == null || nearestDistance > d)
                {
                    nearestPrevious = previousPoint; nearestNext = nextPoint; nearestParameter = parameter; nearestDistance = d;
                }
            }
            else if (d > args.SearchAgainLimit && nearestPrevious != null)
            {
                retVal.Add(CreateResult());
            }
        }

        private FindPositionResult CreateResult()
        {
            var retVal = new FindPositionResult(nearestPrevious, nearestNext, nearestParameter, nearestDistance);
            nearestDistance = 0;
            nearestParameter = 0;
            nearestPrevious = null;
            nearestNext = null;
            return retVal;
        }
    }
}